<?php
/**
 * Ensures that self and static are not used to call public methods in action classes.
 *
 * @author    Greg Sherwood <gsherwood@squiz.net>
 * @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600)
 * @license   https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
 */

namespace PHP_CodeSniffer\Standards\MySource\Sniffs\Channels;

use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Sniffs\Sniff;
use PHP_CodeSniffer\Util\Tokens;

class DisallowSelfActionsSniff implements Sniff
{
    /**
     * Returns an array of tokens this test wants to listen for.
     *
     * @return array
     */
    public function register()
    {
        return [T_CLASS];
    }

    //end register()

    /**
     * Processes this sniff, when one of its tokens is encountered.
     *
     * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
     * @param int                         $stackPtr  The position of the current token in
     *                                               the stack passed in $tokens.
     */
    public function process(File $phpcsFile, $stackPtr)
    {
        $tokens = $phpcsFile->getTokens();

        // We are not interested in abstract classes.
        $prev = $phpcsFile->findPrevious(T_WHITESPACE, ($stackPtr - 1), null, true);
        if (false !== $prev && T_ABSTRACT === $tokens[$prev]['code']) {
            return;
        }

        // We are only interested in Action classes.
        $classNameToken = $phpcsFile->findNext(T_WHITESPACE, ($stackPtr + 1), null, true);
        $className = $tokens[$classNameToken]['content'];
        if ('Actions' !== substr($className, -7)) {
            return;
        }

        $foundFunctions = [];
        $foundCalls = [];

        // Find all static method calls in the form self::method() in the class.
        $classEnd = $tokens[$stackPtr]['scope_closer'];
        for ($i = ($classNameToken + 1); $i < $classEnd; ++$i) {
            if (T_DOUBLE_COLON !== $tokens[$i]['code']) {
                if (T_FUNCTION === $tokens[$i]['code']) {
                    // Cache the function information.
                    $funcName = $phpcsFile->findNext(T_STRING, ($i + 1));
                    $funcScope = $phpcsFile->findPrevious(Tokens::$scopeModifiers, ($i - 1));

                    $foundFunctions[$tokens[$funcName]['content']] = strtolower($tokens[$funcScope]['content']);
                }

                continue;
            }

            $prevToken = $phpcsFile->findPrevious(T_WHITESPACE, ($i - 1), null, true);
            if ('self' !== $tokens[$prevToken]['content']
                && 'static' !== $tokens[$prevToken]['content']
            ) {
                continue;
            }

            $funcNameToken = $phpcsFile->findNext(T_WHITESPACE, ($i + 1), null, true);
            if (T_VARIABLE === $tokens[$funcNameToken]['code']) {
                // We are only interested in function calls.
                continue;
            }

            $funcName = $tokens[$funcNameToken]['content'];

            // We've found the function, now we need to find it and see if it is
            // public, private or protected. If it starts with an underscore we
            // can assume it is private.
            if ('_' === $funcName[0]) {
                continue;
            }

            $foundCalls[$i] = [
                'name' => $funcName,
                'type' => strtolower($tokens[$prevToken]['content']),
            ];
        }//end for

        $errorClassName = substr($className, 0, -7);

        foreach ($foundCalls as $token => $funcData) {
            if (false === isset($foundFunctions[$funcData['name']])) {
                // Function was not in this class, might have come from the parent.
                // Either way, we can't really check this.
                continue;
            }
            if ('public' === $foundFunctions[$funcData['name']]) {
                $type = $funcData['type'];
                $error = "Static calls to public methods in Action classes must not use the {$type} keyword; use %s::%s() instead";
                $data = [
                    $errorClassName,
                    $funcName,
                ];
                $phpcsFile->addError($error, $token, 'Found' . ucfirst($funcData['type']), $data);
            }
        }
    }

    //end process()
}//end class
